﻿#ifndef STRING_CONVERT_UTILS_HPP
#define STRING_CONVERT_UTILS_HPP

#include <iostream>
#include <string>
#include <stdint.h>

namespace mv {
class string_convert_utils {
public:
	static std::string get_bytes(const std::wstring& ucs2_str) {
        std::string utf8_result;
        char utf8_sequence[] = { 0, 0, 0, 0, 0 };
        const wchar_t* cursor = ucs2_str.c_str();
        const wchar_t* const end = ucs2_str.c_str() + ucs2_str.length();
        while (end > cursor) {
            const wchar_t ucs2Char = *cursor;
            ucs2_char2utf8_char_(ucs2Char, utf8_sequence);
            utf8_result.append(utf8_sequence);
            cursor++;
        }
        return utf8_result;
	}

	static std::wstring get_string(const std::string& utf8_str) {
        std::wstring ucs2_result;
        wchar_t ucs2_char2str_buf[] = { 0, 0 };
        const char* cursor = utf8_str.c_str();
        const char* const end = utf8_str.c_str() + utf8_str.length();
        while (end > cursor) {
            uint32_t utf8_tokLen = 1;
            utf8_char2ucs2char_(cursor, &ucs2_char2str_buf[0], &utf8_tokLen);
            ucs2_result.append(ucs2_char2str_buf);
            cursor += utf8_tokLen;
        }
        return ucs2_result;
	}
    //static std::string get_bytes(const std::wstring& value) {
    //	std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
    //	return converter.to_bytes(value);
    //}
    //static std::wstring get_string(const std::string& value) {
    //	std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
    //	return converter.from_bytes(value);
    //}
private:
    static bool utf8_char2ucs2char_(const char* utf82k, wchar_t* ucs2char, uint32_t* utf82k_len) {
        //We do math, that relies on unsigned data types
        const unsigned char* utf82kus = reinterpret_cast<const unsigned char*>(utf82k);

        //Initialize return values for 'return false' cases.
        *ucs2char = L'?';
        *utf82k_len = 1;

        //Decode
        if (0x80 > utf82kus[0]) {
            //Tokensize: 1 byte
            *ucs2char = static_cast<const wchar_t>(utf82kus[0]);
        }
        else if (0xC0 == (utf82kus[0] & 0xE0)) {
            //Tokensize: 2 bytes
            if (0x80 != (utf82kus[1] & 0xC0)) {
                return false;
            }
            *utf82k_len = 2;
            *ucs2char = static_cast<const wchar_t>(
                (utf82kus[0] & 0x1F) << 6
                | (utf82kus[1] & 0x3F)
                );
        }
        else if (0xE0 == (utf82kus[0] & 0xF0)) {
            //Tokensize: 3 bytes
            if ((0x80 != (utf82kus[1] & 0xC0))
                || (0x80 != (utf82kus[2] & 0xC0))
                ) {
                return false;
            }
            *utf82k_len = 3;
            *ucs2char = static_cast<const wchar_t>(
                (utf82kus[0] & 0x0F) << 12
                | (utf82kus[1] & 0x3F) << 6
                | (utf82kus[2] & 0x3F)
                );
        }
        else if (0xF0 == (utf82kus[0] & 0xF8)) {
            //Tokensize: 4 bytes
            *utf82k_len = 4;
            return false;                        //Character exceeds the UCS-2 range (UCS-4 would be necessary)
        }
        else if ((0xF8 == utf82kus[0] & 0xFC)) {
            //Tokensize: 5 bytes
            *utf82k_len = 5;
            return false;                        //Character exceeds the UCS-2 range (UCS-4 would be necessary)
        }
        else if (0xFC == (utf82kus[0] & 0xFE)) {
            //Tokensize: 6 bytes
            *utf82k_len = 6;
            return false;                        //Character exceeds the UCS-2 range (UCS-4 would be necessary)
        }
        else {
            return false;
        }

        return true;
    }

    static void ucs2_char2utf8_char_(const wchar_t ucs2Char, char* utf8Tok) {
        //We do math, that relies on unsigned data types
        uint32_t ucs2CharValue = static_cast<uint32_t>(ucs2Char);   //The standard doesn't specify the signed/unsignedness of wchar_t
        unsigned char* utf8TokUs = reinterpret_cast<unsigned char*>(utf8Tok);

        //Decode
        if (0x80 > ucs2CharValue) {
            //Tokensize: 1 byte
            utf8TokUs[0] = static_cast<unsigned char>(ucs2CharValue);
            utf8TokUs[1] = '\0';
        }
        else if (0x800 > ucs2CharValue) {
            //Tokensize: 2 bytes
            utf8TokUs[2] = '\0';
            utf8TokUs[1] = static_cast<unsigned char>(0x80 | (ucs2CharValue & 0x3F));
            ucs2CharValue = (ucs2CharValue >> 6);
            utf8TokUs[0] = static_cast<unsigned char>(0xC0 | ucs2CharValue);
        }
        else {
            //Tokensize: 3 bytes
            utf8TokUs[3] = '\0';
            utf8TokUs[2] = static_cast<unsigned char>(0x80 | (ucs2CharValue & 0x3F));
            ucs2CharValue = (ucs2CharValue >> 6);
            utf8TokUs[1] = static_cast<unsigned char>(0x80 | (ucs2CharValue & 0x3F));
            ucs2CharValue = (ucs2CharValue >> 6);
            utf8TokUs[0] = static_cast<unsigned char>(0xE0 | ucs2CharValue);
        }
    }
};
} // namespace mv

#endif // !STRING_CONVERT_UTILS_HPP
